package com.cch.cooperation.api.auth.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cch.cooperation.api.auth.dto.*;
import com.cch.cooperation.api.auth.event.UserAddEvent;
import com.cch.cooperation.api.auth.event.UserLoginEvent;
import com.cch.cooperation.api.blacklist.service.BlacklistServiceImpl;
import com.cch.cooperation.api.common.request.IdReq;
import com.cch.cooperation.api.common.sdk.apple.AppleClient;
import com.cch.cooperation.api.common.sdk.apple.model.AppleIdTokenJwt;
import com.cch.cooperation.api.common.sdk.umeng.UmengClient;
import com.cch.cooperation.api.common.util.PhoneNumberUtil;
import com.cch.cooperation.api.support.service.SmsServiceImpl;
import com.cch.cooperation.biz.common.lock.Locker;
import com.cch.cooperation.biz.common.util.BeanUtilEx;
import com.cch.cooperation.biz.common.util.SpringUtil;
import com.cch.cooperation.common.exception.BizException;
import com.cch.cooperation.common.util.SnowflakeIdUtil;
import com.cch.cooperation.dao.auth.enums.UserEnum;
import com.cch.cooperation.dao.auth.mapper.UserMapper;
import com.cch.cooperation.dao.auth.model.User;
import com.cch.cooperation.dao.blacklist.model.Blacklist;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl {

    @Resource
    private UserMapper mapper;
    @Resource
    private SmsServiceImpl smsService;
    @Resource
    private BlacklistServiceImpl blacklistService;
    @Resource
    private AppleClient appleClient;
    @Resource
    private UmengClient umengClient;

    @Locker(key = "'LOCK:AUTH:USER:CREATE_IF_ABSENT:' + #req.username")
    public LoginUserDTO loginBySmsCode(@Valid LoginReqDTO req) {
        smsService.useCode(req.getUsername(), req.getSmsCode());
        // 用户不存在且存在游客账户时，将游客账户转为正常账户
        User user = getByUsername(req.getUsername());
        if (Objects.isNull(user) && StrUtil.isNotEmpty(req.getTouristId())) {
            touristToNormal(req.getUsername(), null, req.getTouristId());
        }
        User loginUser = createIfAbsentByUsername(req.getUsername(), UserEnum.Type.NORMAL.value);
        if (UserEnum.Status.CANCELLATION.value.equals(loginUser.getStatus())) {
            throw new BizException("该用户已注销");
        }
        // 发送登录事件
        SpringUtil.getApplicationContext().publishEvent(new UserLoginEvent(this, loginUser));
        return BeanUtilEx.copy(loginUser, LoginUserDTO.class);
    }

    /**
     * 使用 Apple 授权码登录
     *
     * @param req Apple 登录请求参数
     * @return 登录用户信息
     */
    public LoginUserDTO loginByApple(@Valid AppleLoginReqDTO req) throws Exception {
        AppleIdTokenJwt jwt = appleClient.parseIdToken(req.getIdToken());
        if (Objects.isNull(jwt)) {
            throw new BizException("Apple ID Token 无效");
        }
        User user = getByAppleUserId(jwt.getSub());
        if (Objects.isNull(user) && StrUtil.isNotEmpty(req.getTouristId())) {
            touristToNormal(null, jwt.getSub(), req.getTouristId());
        }
        User loginUser = createIfAbsentByAppleUserId(jwt.getSub());
        if (UserEnum.Status.CANCELLATION.value.equals(loginUser.getStatus())) {
            throw new BizException("该用户已注销");
        }
        // 发送登录事件
        SpringUtil.getApplicationContext().publishEvent(new UserLoginEvent(this, loginUser));
        return BeanUtilEx.copy(loginUser, LoginUserDTO.class);
    }

    public LoginUserDTO loginByTourist(@Valid IdReq req) {
        User user = createIfAbsentByUsername(req.getId(), UserEnum.Type.TOURIST.value);
        if (UserEnum.Status.CANCELLATION.value.equals(user.getStatus())) {
            throw new BizException("该用户已注销");
        }
        user.setNickname("游客用户");
        mapper.updateById(user);
        // 发送登录事件
        SpringUtil.getApplicationContext().publishEvent(new UserLoginEvent(this, user));
        return BeanUtilEx.copy(user, LoginUserDTO.class);
    }

    public LoginUserDTO loginByPhoneNumber(@Valid PhoneNumberLoginReqDTO req) {
        String phoneNumber = umengClient.getPhoneNumber(req.getToken());
        // 用户不存在且存在游客账户时，将游客账户转为正常账户
        User user = getByUsername(phoneNumber);
        if (Objects.isNull(user) && StrUtil.isNotEmpty(req.getTouristId())) {
            touristToNormal(phoneNumber, null, req.getTouristId());
        }
        User loginUser = createIfAbsentByUsername(phoneNumber, UserEnum.Type.NORMAL.value);
        if (UserEnum.Status.CANCELLATION.value.equals(loginUser.getStatus())) {
            throw new BizException("该用户已注销");
        }
        // 发送登录事件
        SpringUtil.getApplicationContext().publishEvent(new UserLoginEvent(this, loginUser));
        return BeanUtilEx.copy(loginUser, LoginUserDTO.class);
    }


    @Locker(key = "'LOCK:AUTH:USER:BIND_PHONE_NUMBER:' + #req.phoneNumber")
    public void bindPhoneNumber(@Valid BindPhoneReqDTO req) {
        smsService.useCode(req.getPhoneNumber(), req.getSmsCode());
        User exists = getByUsername(req.getPhoneNumber());
        if (Objects.nonNull(exists)) {
            throw new BizException("该手机号已被绑定");
        }
        User user = mapper.selectById(req.getId());
        user.setUsername(req.getPhoneNumber());
        user.setType(UserEnum.Type.NORMAL.value);
        mapper.updateById(user);
    }

    public void touristToNormal(String username, String appleUserId,String touristId) {
        User user = createIfAbsentByUsername(touristId, UserEnum.Type.TOURIST.value);
        user.setUsername(username);
        user.setAppleUserId(appleUserId);
        user.setNickname(PhoneNumberUtil.mask(username));
        user.setType(UserEnum.Type.NORMAL.value);
        mapper.updateById(user);
    }

    public LoginUserDTO updateById(@Valid UserUpdateDTO req) {
        User user = mapper.selectById(req.getId());
        if (ObjectUtil.isNull(user)) {
            throw new BizException("该用户不存在");
        }
        if (Objects.equals(user.getType(), UserEnum.Type.TOURIST.value)) {
            throw  new BizException("游客用户暂不支持该功能");
        }
        user.setNickname(req.getNickname());
        user.setAvatarUrl(req.getAvatarUrl());
        user.setSex(req.getSex());
        mapper.updateById(user);
        user = mapper.selectById(req.getId());
        return BeanUtilEx.copy(user, LoginUserDTO.class);
    }

    public void cancellation(String id) {
        User user = new User();
        user.setId(id);
        user.setStatus(UserEnum.Status.CANCELLATION.value);
        user.setUpdateTime(new Date());
        mapper.updateById(user);
    }

    @Locker(key = "'LOCK:AUTH:USER:CREATE_IF_ABSENT:' + #username")
    public User createIfAbsentByUsername(String username, int type) {
        User user = getByUsername(username);
        if (ObjectUtil.isNotNull(user)) {
            user.setIsNewCreate(false);
            return user;
        }
        user = new User();
        user.setId(SnowflakeIdUtil.getIdStr());
        user.setUsername(username);
        user.setNickname(PhoneNumberUtil.mask(username));
        user.setCreateTime(new Date());
        user.setType(type);
        user.setIsNewCreate(true);
        mapper.insert(user);
        SpringUtil.getApplicationContext().publishEvent(new UserAddEvent(this, user));
        return user;
    }

    @Locker(key = "'LOCK:AUTH:USER:CREATE_IF_ABSENT_BY_APPLE_USER_ID:' + #appleUserId")
    public User createIfAbsentByAppleUserId(String appleUserId) {
        User user = getByAppleUserId(appleUserId);
        if (ObjectUtil.isNotNull(user)) {
            user.setIsNewCreate(false);
            return user;
        }
        user = new User();
        user.setId(SnowflakeIdUtil.getIdStr());
        user.setAppleUserId(appleUserId);
        user.setNickname("苹果用户");
        user.setCreateTime(new Date());
        user.setType(UserEnum.Type.NORMAL.value);
        user.setIsNewCreate(true);
        mapper.insert(user);
        SpringUtil.getApplicationContext().publishEvent(new UserAddEvent(this, user));
        return user;
    }

    public User getByUsername(String username) {
        LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
        query.eq(User::getUsername, username);
        query.last("limit 1");
        return mapper.selectOne(query);
    }

    public User getByAppleUserId(String appleUserId) {
        LambdaQueryWrapper<User> query = new LambdaQueryWrapper<>();
        query.eq(User::getAppleUserId, appleUserId);
        query.last("limit 1");
        return mapper.selectOne(query);
    }

    public List<User> queryByIds(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        return mapper.selectBatchIds(ids);
    }

    public List<User> queryAll() {
        return mapper.selectList(null);
    }

    public User getById(String id) {
        return mapper.selectById(id);
    }

    public Map<String, User> getMapByIds(List<String> ids) {
        return queryByIds(ids).stream().collect(Collectors.toMap(o -> o.getId(), o -> o, (o1, o2) -> o1));
    }

    public void addBlacklist(String userId, String targetId) {
        Blacklist blacklist = new Blacklist();
        blacklist.setUserId(userId);
        blacklist.setTargetId(targetId);
        blacklistService.add(blacklist);
    }

    public void deleteBlacklist(String userId, String targetId) {
        blacklistService.delete(userId, targetId);
    }

    public List<User> queryBlacklist(String userId) {
        List<Blacklist> blacklists = blacklistService.queryList(userId);
        return queryByIds(blacklists.stream().map(o -> o.getTargetId()).collect(Collectors.toList()));
    }

    public User renewVip(@Valid VipRenewDTO req) {
        User user = mapper.selectById(req.getUserId());
        Date baseVipExpireTime = new Date();
        if (Objects.nonNull(user.getVipExpireTime()) && user.getVipExpireTime().after(baseVipExpireTime)) {
            baseVipExpireTime = user.getVipExpireTime();
        }
        user.setVipExpireTime(DateUtil.offset(baseVipExpireTime, DateField.SECOND, req.getSecond()));
        mapper.updateById(user);
        return user;
    }

}
